#include "Interpreter.h"

namespace Signal
{
	Interpreter::Interpreter (Environment& env)
	:
		m_env (env)
	{}

	void Interpreter::execute ()
	{
		std::shared_ptr<Function> func = m_env.find_func ("main");

		if (func.get () == nullptr) {
			throw Error ("Interpreter : main () does not exist.");
		}

		m_frames.push (CallFrame (func));
		m_scopes.push (func->scope ());

		while (m_frames.size () > 0) {
			CallFrame& frame = m_frames.top ();
			Instruction& instruction = (*frame.m_func->code ())[frame.m_address];

			frame.m_address++;

			switch (instruction.m_op)
			{
				case OP_PUSH: m_stack.push (instruction.m_object); break;
				case OP_POP:  m_stack.pop (); break;
				case OP_NIL:  m_stack.push (m_env.obj_nil ()); break;

				case OP_CALL:
				{
					std::shared_ptr<Function> call_func = m_env.find_func (dynamic_cast<String*> (instruction.m_object.get ())->text ());

					if (call_func.get () == nullptr) {
						throw Error ("Interpreter : function '%s' does not exist.", dynamic_cast<String*> (instruction.m_object.get ())->text ());
					}

					m_frames.push (CallFrame (call_func));
					call_func->scope ()->reset ();
					m_scopes.push (std::shared_ptr<Scope> (new Scope (*call_func->scope ())));
				}
				break;

				case OP_MCALL:
				{
					double_t num_args = dynamic_cast<Number*> (m_stack.top ().get ())->number ();
					m_stack.pop ();

					std::shared_ptr<Object> instance = m_stack.top ();
					m_stack.pop ();


				}
				break;

				case OP_RETURN:
				{
					m_frames.pop ();
					m_scopes.pop ();
				}
				break;

				case OP_PRINT:
				{
					std::shared_ptr<Object> arg = m_stack.top ();

					switch (arg->type ())
					{
						case Object::NUMBER:
						{
							std::cout << dynamic_cast<Number*> (arg.get ())->number ();
						}
						break;

						case Object::STRING:
						{
							if (dynamic_cast<String*> (arg.get ())->text () == "\\n") {
								std::cout << std::endl;
							} else {
								std::cout << dynamic_cast<String*> (arg.get ())->text ();
							}
						}
						break;

						case Object::TRUE:
						{
							std::cout << "true";
						}
						break;

						case Object::FALSE:
						{
							std::cout << "false";
						}
						break;

						case Object::NIL:
						{
							std::cout << "nil";
						}
						break;

						default:
						{
							throw Error ("Interpreter : Invalid argument to print.");
						}
					}
				}
				break;

				case OP_SET:
				{
					const std::string& name = dynamic_cast<String*> (instruction.m_object.get ())->text ();
					m_scopes.top ()->set (name, m_stack.top ());
				}
				break;

				case OP_REF:
				{
					const std::string& name = dynamic_cast<String*> (instruction.m_object.get ())->text ();
					m_stack.push (m_scopes.top ()->find (name));
				}
				break;

				case OP_BR:
				{
					frame.m_address = instruction.m_arg;
					m_stack.pop ();
				}
				break;

				case OP_BRT:
				{
					if (m_stack.top ()->type () == Object::TRUE) {
						frame.m_address = instruction.m_arg;
					}
					m_stack.pop ();
				}
				break;

				case OP_BRF:
				{
					if (m_stack.top ()->type () == Object::FALSE) {
						frame.m_address = instruction.m_arg;
					}
					m_stack.pop ();
				}
				break;

				case OP_ADD:
				{
					std::shared_ptr<Object> right = m_stack.top ();
					m_stack.pop ();

					std::shared_ptr<Object> left = m_stack.top ();;
					m_stack.pop ();
					
					switch (left->type ())
					{
						case Object::NUMBER:
						{
							switch (right->type ())
							{
								case Object::NUMBER:
								{
									m_stack.push (std::shared_ptr<Object> (new Number (dynamic_cast<Number*> (left.get ())->number () + dynamic_cast<Number*> (right.get ())->number ())));
								}
								break;

								default:
								{
									throw Error ("Interpreter : Type mismatch on operator '+'.");
								}
								break;
							}
						}
						break;

						case Object::STRING:
						{
							switch (right->type ())
							{
								case Object::STRING:
								{
									m_stack.push (std::shared_ptr<Object> (new String (dynamic_cast<String*> (left.get ())->text () + dynamic_cast<String*> (right.get ())->text ())));
								}
								break;

								default:
								{
									throw Error ("Interpreter : Type mismatch on operator '+'.");
								}
								break;
							}
						}
						break;

						default:
						{
							throw Error ("Interpreter : Invalid arguments to operator '+'.");
						}
						break;
					}
				}
				break;

				case OP_SUB:
				{
					std::shared_ptr<Object> right = m_stack.top ();
					m_stack.pop ();

					std::shared_ptr<Object> left = m_stack.top ();;
					m_stack.pop ();
					
					switch (left->type ())
					{
						case Object::NUMBER:
						{
							switch (right->type ())
							{
								case Object::NUMBER:
								{
									m_stack.push (std::shared_ptr<Object> (new Number (dynamic_cast<Number*> (left.get ())->number () - dynamic_cast<Number*> (right.get ())->number ())));
								}
								break;

								default:
								{
									throw Error ("Interpreter : Type mismatch on operator '-'.");
								}
								break;
							}
						}
						break;

						default:
						{
							throw Error ("Interpreter : Invalid arguments to operator '-'.");
						}
						break;
					}
				}
				break;

				case OP_MUL:
				{
					std::shared_ptr<Object> right = m_stack.top ();
					m_stack.pop ();

					std::shared_ptr<Object> left = m_stack.top ();;
					m_stack.pop ();
					
					switch (left->type ())
					{
						case Object::NUMBER:
						{
							switch (right->type ())
							{
								case Object::NUMBER:
								{
									m_stack.push (std::shared_ptr<Object> (new Number (dynamic_cast<Number*> (left.get ())->number () * dynamic_cast<Number*> (right.get ())->number ())));
								}
								break;

								default:
								{
									throw Error ("Interpreter : Type mismatch on operator '*'.");
								}
								break;
							}
						}
						break;

						default:
						{
							throw Error ("Interpreter : Invalid arguments to operator '*'.");
						}
						break;
					}
				}
				break;

				case OP_DIV:
				{
					std::shared_ptr<Object> right = m_stack.top ();
					m_stack.pop ();

					std::shared_ptr<Object> left = m_stack.top ();;
					m_stack.pop ();
					
					switch (left->type ())
					{
						case Object::NUMBER:
						{
							switch (right->type ())
							{
								case Object::NUMBER:
								{
									if (dynamic_cast<Number*> (right.get ())->number () == 0) {
										m_stack.push (std::shared_ptr<Object> (new Nil ()));
									} else {
										m_stack.push (std::shared_ptr<Object> (new Number (dynamic_cast<Number*> (left.get ())->number () / dynamic_cast<Number*> (right.get ())->number ())));
									}
								}
								break;

								default:
								{
									throw Error ("Interpreter : Type mismatch on operator '/'.");
								}
								break;
							}
						}
						break;

						default:
						{
							throw Error ("Interpreter : Invalid arguments to operator '/'.");
						}
						break;
					}
				}
				break;

				case OP_NEG:
				{
					std::shared_ptr<Object> value = m_stack.top ();
					m_stack.pop ();
					
					switch (value->type ())
					{
						case Object::NUMBER:
						{
							m_stack.push (std::shared_ptr<Object> (new Number (-dynamic_cast<Number*> (value.get ())->number ())));
						}
						break;

						default:
						{
							throw Error ("Interpreter : Invalid arguments to operator '-'.");
						}
						break;
					}
				}
				break;

				case OP_NOT:
				{
					std::shared_ptr<Object> value = m_stack.top ();
					m_stack.pop ();
					
					switch (value->type ())
					{
						case Object::TRUE:
						{
							m_stack.push (std::shared_ptr<Object> (new False ()));
						}
						break;

						case Object::FALSE:
						{
							m_stack.push (std::shared_ptr<Object> (new True ()));
						}
						break;

						default:
						{
							throw Error ("Interpreter : Invalid arguments to operator '!'.");
						}
						break;
					}
				}
				break;

				case OP_OR:
				{
					std::shared_ptr<Object> right = m_stack.top ();
					m_stack.pop ();

					std::shared_ptr<Object> left = m_stack.top ();;
					m_stack.pop ();
					
					switch (left->type ())
					{
						case Object::TRUE:
						{
							switch (right->type ())
							{
								case Object::TRUE:
								{
									m_stack.push (std::shared_ptr<Object> (new True ()));
								}
								break;

								case Object::FALSE:
								{
									m_stack.push (std::shared_ptr<Object> (new True ()));
								}
								break;

								default:
								{
									throw Error ("Interpreter : Type mismatch on operator '||'.");
								}
								break;
							}
						}
						break;

						case Object::FALSE:
						{
							switch (right->type ())
							{
								case Object::TRUE:
								{
									m_stack.push (std::shared_ptr<Object> (new True ()));
								}
								break;

								case Object::FALSE:
								{
									m_stack.push (std::shared_ptr<Object> (new False ()));
								}
								break;

								default:
								{
									throw Error ("Interpreter : Type mismatch on operator '||'.");
								}
								break;
							}
						}
						break;

						default:
						{
							throw Error ("Interpreter : Invalid arguments to operator '||'.");
						}
						break;
					}
				}
				break;

				case OP_AND:
				{
					std::shared_ptr<Object> right = m_stack.top ();
					m_stack.pop ();

					std::shared_ptr<Object> left = m_stack.top ();;
					m_stack.pop ();
					
					switch (left->type ())
					{
						case Object::TRUE:
						{
							switch (right->type ())
							{
								case Object::TRUE:
								{
									m_stack.push (std::shared_ptr<Object> (new True ()));
								}
								break;

								case Object::FALSE:
								{
									m_stack.push (std::shared_ptr<Object> (new False ()));
								}
								break;

								default:
								{
									throw Error ("Interpreter : Type mismatch on operator '||'.");
								}
								break;
							}
						}
						break;

						case Object::FALSE:
						{
							switch (right->type ())
							{
								case Object::TRUE:
								{
									m_stack.push (std::shared_ptr<Object> (new False ()));
								}
								break;

								case Object::FALSE:
								{
									m_stack.push (std::shared_ptr<Object> (new False ()));
								}
								break;

								default:
								{
									throw Error ("Interpreter : Type mismatch on operator '||'.");
								}
								break;
							}
						}
						break;

						default:
						{
							throw Error ("Interpreter : Invalid arguments to operator '||'.");
						}
						break;
					}
				}
				break;

				case OP_EQEQ:
				{
					std::shared_ptr<Object> right = m_stack.top ();
					m_stack.pop ();

					std::shared_ptr<Object> left = m_stack.top ();;
					m_stack.pop ();

					switch (left->type ())
					{
						case Object::TRUE:
						{
							switch (right->type ())
							{
								case Object::TRUE:
								{
									m_stack.push (std::shared_ptr<Object> (new True ()));
								}
								break;

								default:
								{
									m_stack.push (std::shared_ptr<Object> (new False ()));
								}
								break;
							}
						}
						break;

						case Object::FALSE:
						{
							switch (right->type ())
							{
								case Object::FALSE:
								{
									m_stack.push (std::shared_ptr<Object> (new True ()));
								}
								break;

								default:
								{
									m_stack.push (std::shared_ptr<Object> (new False ()));
								}
								break;
							}
						}
						break;

						case Object::NIL:
						{
							switch (right->type ())
							{
								case Object::NIL:
								{
									m_stack.push (std::shared_ptr<Object> (new True ()));
								}
								break;

								default:
								{
									m_stack.push (std::shared_ptr<Object> (new False ()));
								}
								break;
							}
						}
						break;

						case Object::NUMBER:
						{
							switch (right->type ())
							{
								case Object::NUMBER:
								{
									if (dynamic_cast<Number*> (left.get ())->number () == dynamic_cast<Number*> (right.get ())->number ()) {
										m_stack.push (std::shared_ptr<Object> (new True ()));
									} else {
										m_stack.push (std::shared_ptr<Object> (new False ()));
									}
								}
								break;

								default:
								{
									m_stack.push (std::shared_ptr<Object> (new False ()));
								}
								break;
							}
						}
						break;

						case Object::STRING:
						{
							switch (right->type ())
							{
								case Object::STRING:
								{
									if (dynamic_cast<String*> (left.get ())->text () == dynamic_cast<String*> (right.get ())->text ()) {
										m_stack.push (std::shared_ptr<Object> (new True ()));
									} else {
										m_stack.push (std::shared_ptr<Object> (new False ()));
									}
								}
								break;

								default:
								{
									m_stack.push (std::shared_ptr<Object> (new False ()));
								}
								break;
							}
						}
						break;

						case Object::INSTANCE:
						{
							switch (right->type ())
							{
								case Object::INSTANCE:
								{
									if (left.get () == right.get ()) {
										m_stack.push (std::shared_ptr<Object> (new True ()));
									} else {
										m_stack.push (std::shared_ptr<Object> (new False ()));
									}
								}
								break;

								default:
								{
									m_stack.push (std::shared_ptr<Object> (new False ()));
								}
								break;
							}
						}
						break;
					}
				}
				break;

				case OP_NEQ:
				{
					std::shared_ptr<Object> right = m_stack.top ();
					m_stack.pop ();

					std::shared_ptr<Object> left = m_stack.top ();;
					m_stack.pop ();

					switch (left->type ())
					{
						case Object::TRUE:
						{
							switch (right->type ())
							{
								case Object::TRUE:
								{
									m_stack.push (std::shared_ptr<Object> (new False ()));
								}
								break;

								default:
								{
									m_stack.push (std::shared_ptr<Object> (new True ()));
								}
								break;
							}
						}
						break;

						case Object::FALSE:
						{
							switch (right->type ())
							{
								case Object::FALSE:
								{
									m_stack.push (std::shared_ptr<Object> (new False ()));
								}
								break;

								default:
								{
									m_stack.push (std::shared_ptr<Object> (new True ()));
								}
								break;
							}
						}
						break;

						case Object::NIL:
						{
							switch (right->type ())
							{
								case Object::NIL:
								{
									m_stack.push (std::shared_ptr<Object> (new False ()));
								}
								break;

								default:
								{
									m_stack.push (std::shared_ptr<Object> (new True ()));
								}
								break;
							}
						}
						break;

						case Object::NUMBER:
						{
							switch (right->type ())
							{
								case Object::NUMBER:
								{
									if (dynamic_cast<Number*> (left.get ())->number () != dynamic_cast<Number*> (right.get ())->number ()) {
										m_stack.push (std::shared_ptr<Object> (new True ()));
									} else {
										m_stack.push (std::shared_ptr<Object> (new False ()));
									}
								}
								break;

								default:
								{
									m_stack.push (std::shared_ptr<Object> (new False ()));
								}
								break;
							}
						}
						break;

						case Object::STRING:
						{
							switch (right->type ())
							{
								case Object::STRING:
								{
									if (dynamic_cast<String*> (left.get ())->text () != dynamic_cast<String*> (right.get ())->text ()) {
										m_stack.push (std::shared_ptr<Object> (new True ()));
									} else {
										m_stack.push (std::shared_ptr<Object> (new False ()));
									}
								}
								break;

								default:
								{
									m_stack.push (std::shared_ptr<Object> (new False ()));
								}
								break;
							}
						}
						break;

						case Object::INSTANCE:
						{
							switch (right->type ())
							{
								case Object::INSTANCE:
								{
									if (left.get () != right.get ()) {
										m_stack.push (std::shared_ptr<Object> (new True ()));
									} else {
										m_stack.push (std::shared_ptr<Object> (new False ()));
									}
								}
								break;

								default:
								{
									m_stack.push (std::shared_ptr<Object> (new False ()));
								}
								break;
							}
						}
						break;
					}
				}
				break;

				case OP_LT:
				{
					std::shared_ptr<Object> right = m_stack.top ();
					m_stack.pop ();

					std::shared_ptr<Object> left = m_stack.top ();;
					m_stack.pop ();
					
					switch (left->type ())
					{
						case Object::NUMBER:
						{
							switch (right->type ())
							{
								case Object::NUMBER:
								{
									if (dynamic_cast<Number*> (left.get ())->number () < dynamic_cast<Number*> (right.get ())->number ()) {
										m_stack.push (m_env.obj_true ());
									} else {
										m_stack.push (m_env.obj_false ());
									}
								}
								break;

								default:
								{
									throw Error ("Interpreter : Type mismatch on operator '<'.");
								}
								break;
							}
						}
						break;

						default:
						{
							throw Error ("Interpreter : Invalid arguments to operator '<'.");
						}
						break;
					}
				}
				break;

				case OP_GT:
				{
					std::shared_ptr<Object> right = m_stack.top ();
					m_stack.pop ();

					std::shared_ptr<Object> left = m_stack.top ();;
					m_stack.pop ();
					
					switch (left->type ())
					{
						case Object::NUMBER:
						{
							switch (right->type ())
							{
								case Object::NUMBER:
								{
									if (dynamic_cast<Number*> (left.get ())->number () > dynamic_cast<Number*> (right.get ())->number ()) {
										m_stack.push (m_env.obj_true ());
									} else {
										m_stack.push (m_env.obj_false ());
									}
								}
								break;

								default:
								{
									throw Error ("Interpreter : Type mismatch on operator '>'.");
								}
								break;
							}
						}
						break;

						default:
						{
							throw Error ("Interpreter : Invalid arguments to operator '>'.");
						}
						break;
					}
				}
				break;

				case OP_LTE:
				{
					std::shared_ptr<Object> right = m_stack.top ();
					m_stack.pop ();

					std::shared_ptr<Object> left = m_stack.top ();;
					m_stack.pop ();
					
					switch (left->type ())
					{
						case Object::NUMBER:
						{
							switch (right->type ())
							{
								case Object::NUMBER:
								{
									if (dynamic_cast<Number*> (left.get ())->number () <= dynamic_cast<Number*> (right.get ())->number ()) {
										m_stack.push (m_env.obj_true ());
									} else {
										m_stack.push (m_env.obj_false ());
									}
								}
								break;

								default:
								{
									throw Error ("Interpreter : Type mismatch on operator '<='.");
								}
								break;
							}
						}
						break;

						default:
						{
							throw Error ("Interpreter : Invalid arguments to operator '<='.");
						}
						break;
					}
				}
				break;

				case OP_GTE:
				{
					std::shared_ptr<Object> right = m_stack.top ();
					m_stack.pop ();

					std::shared_ptr<Object> left = m_stack.top ();;
					m_stack.pop ();
					
					switch (left->type ())
					{
						case Object::NUMBER:
						{
							switch (right->type ())
							{
								case Object::NUMBER:
								{
									if (dynamic_cast<Number*> (left.get ())->number () >= dynamic_cast<Number*> (right.get ())->number ()) {
										m_stack.push (m_env.obj_true ());
									} else {
										m_stack.push (m_env.obj_false ());
									}
								}
								break;

								default:
								{
									throw Error ("Interpreter : Type mismatch on operator '>='.");
								}
								break;
							}
						}
						break;

						default:
						{
							throw Error ("Interpreter : Invalid arguments to operator '>='.");
						}
						break;
					}
				}
				break;
			}
		}
	}
}